package org.summerframework.core.jackson;

import org.summerframework.core.base.service.BaseService;
import org.summerframework.core.util.OptionalUtils;
import org.summerframework.core.util.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.core.annotation.AnnotationUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author 石超
 * @version v1.0.0
 */
@Slf4j
public class SelectByIdSerializer {
    private static final Map<Class, Map<Field, Map<String, Method>>> selectByIdHashMap = new HashMap<>();
    private static final Map<Class, Map<Field, Map<String, Method>>> selectByQueryHashMap = new HashMap<>();
    private static final Map<Class, Map<JoinList, Field>> joinListHashMap = new HashMap<>();
    private static final Map<JoinList, Map<String, Field>> jsonJoinListFieldHashMap = new HashMap<>();
    private static final String SET_METHOD_START = "set";
    private static final String GET_METHOD_START = "get";

    public static <T> T serializer(T o) {
        Map<Field, Map<String, Method>> fieldMethods;
        Map<Field, Map<String, Method>> selectByQueryMethods;

        if (!selectByIdHashMap.containsKey(o.getClass()) || !joinListHashMap.containsKey(o.getClass())) {
            fieldMethods = selectByIdHashMap.computeIfAbsent(o.getClass(), key -> new HashMap<>(16));
            Map<JoinList, Field> jsonJoinLists = joinListHashMap.computeIfAbsent(o.getClass(), (k) -> new HashMap<>(16));
            Arrays.stream(o.getClass().getDeclaredFields()).forEach(field -> {
                SelectById annotation = AnnotationUtils.findAnnotation(field, SelectById.class);
                JoinList jsonJoinList = AnnotationUtils.findAnnotation(field, JoinList.class);
                if (annotation != null) {
                    field.setAccessible(true);
                    fieldMethods.put(field, new HashMap<>(16));
                }

                if (jsonJoinList != null) {
                    try {
                        Map<String, Field> stringFieldMap = jsonJoinListFieldHashMap.computeIfAbsent(jsonJoinList, k -> new HashMap<>(16));
                        Field declaredField = o.getClass().getDeclaredField(jsonJoinList.target());
                        declaredField.setAccessible(true);
                        jsonJoinLists.put(jsonJoinList, declaredField);
                        field.setAccessible(true);
                        stringFieldMap.put(SET_METHOD_START, field);
                    } catch (NoSuchFieldException e) {
                        e.printStackTrace();
                    }
                }
            });
        } else {
            fieldMethods = selectByIdHashMap.get(o.getClass());
        }


        if (!selectByQueryHashMap.containsKey(o.getClass())) {
            selectByQueryMethods = selectByQueryHashMap.computeIfAbsent(o.getClass(), key -> new HashMap<>(16));

            SelectByQuery annotation1 = AnnotationUtils.findAnnotation(o.getClass(), SelectByQuery.class);
            List<Field> declaredFields = new ArrayList<>(Arrays.asList(o.getClass().getDeclaredFields()));

            if (o.getClass().getSuperclass() != null) {
                declaredFields.addAll(Arrays.asList(o.getClass().getSuperclass().getDeclaredFields()));
            }

            declaredFields.forEach(field -> {
                SelectByQuery annotation = annotation1 != null && "id".equals(field.getName()) ? annotation1 : AnnotationUtils.findAnnotation(field, SelectByQuery.class);
                if (annotation != null) {
                    field.setAccessible(true);
                    Map<String, Method> value = new HashMap<>(16);
                    selectByQueryMethods.put(field, value);
                    try {
                        Method method = getService(annotation).getClass().getMethod(annotation.method(), annotation.param());
                        method.setAccessible(true);
                        value.put("select", method);
                    } catch (NoSuchMethodException e) {
                        selectByQueryMethods.put(field, null);
                    }
                }
            });
        } else {
            selectByQueryMethods = selectByQueryHashMap.get(o.getClass());
        }

        selectByQueryMethods.forEach((field, methods) -> {
            Method method = methods.get("select");
            SelectByQuery annotation = "id".equals(field.getName()) ? AnnotationUtils.findAnnotation(o.getClass(), SelectByQuery.class) : AnnotationUtils.findAnnotation(field, SelectByQuery.class);
            try {
                Object value = field.get(o);
                if (annotation == null || method == null || value == null || value.getClass() != annotation.param()) {
                    return;
                }

                if (!methods.containsKey(SET_METHOD_START)) {
                    Method aClass = getClass(o.getClass(), annotation.name(), List.class);
                    methods.put(SET_METHOD_START, aClass);
                }

                if (methods.get(SET_METHOD_START) == null) {
                    return;
                }

                Object invoke = method.invoke(getService(annotation), value);
                methods.get(SET_METHOD_START).invoke(o, invoke);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        joinListHashMap.get(o.getClass()).forEach((jsonJoinList, field1) -> {
            try {
                Object invoke = field1.get(o);
                Map<String, Field> stringFieldMap = jsonJoinListFieldHashMap.computeIfAbsent(jsonJoinList, k -> new HashMap<>(16));

                if (invoke instanceof List && CollectionUtils.isNotEmpty((List) invoke) && stringFieldMap.get(SET_METHOD_START) != null) {
                    List list = (List) invoke;
                    if (!stringFieldMap.containsKey(GET_METHOD_START)) {
                        Field declaredField = null;
                        try {
                            declaredField = list.get(0).getClass().getDeclaredField(jsonJoinList.fieldName());
                            declaredField.setAccessible(true);
                            stringFieldMap.put(GET_METHOD_START, declaredField);
                        } catch (NoSuchFieldException ignored) {
                            jsonJoinListFieldHashMap.put(jsonJoinList, null);
                        }
                    }

                    try {
                        if (stringFieldMap.get(GET_METHOD_START) == null) {
                            return;
                        }

                        List<Object> set = new ArrayList<>();

                        for (Object obj : list) {
                            set.add(stringFieldMap.get(GET_METHOD_START).get(obj));

                        }

                        stringFieldMap.get(SET_METHOD_START).set(o, set);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        });

        fieldMethods.forEach(((field, methods) -> {
            SelectById selectById = AnnotationUtils.getAnnotation(field, SelectById.class);
            if (selectById != null) {
                Object object = null;

                try {
                    Object bean = getService(selectById);

                    if (bean instanceof BaseService) {
                        object = OptionalUtils.get(((BaseService) bean).selectById((Long) field.get(o)));
                    }
                } catch (Exception e) {
                    log.error("转换失败", e);
                    return;
                }


                if (ArrayUtils.isNotEmpty(selectById.names())) {
                    Object finalObject = object;
                    Arrays.stream(selectById.names()).forEach(selectByIdName -> {
                        try {
                            add(field.getName(), o, methods, finalObject, selectByIdName);
                        } catch (IOException e) {
                            log.error("转换失败", e);
                        }
                    });
                } else {
                    try {
                        add(field.getName(), o, methods, object, selectById);
                    } catch (IOException e) {
                        log.error("转换失败", e);
                    }
                }
            }
        }));

        return o;
    }

    private static <T> T getService(SelectById selectById) {
        if (selectById.beanClass() != Void.class) {
            return (T) SpringContextUtil.getBean(selectById.beanClass());
        } else if (StringUtils.isNotBlank(selectById.beanName())) {
            return SpringContextUtil.getBean(selectById.beanName());
        }

        throw new NoSuchBeanDefinitionException("not bean");
    }

    private static <T> T getService(SelectByQuery selectByQuery) {
        if (selectByQuery.beanClass() != Void.class) {
            return (T) SpringContextUtil.getBean(selectByQuery.beanClass());
        } else if (StringUtils.isNotBlank(selectByQuery.beanName())) {
            return SpringContextUtil.getBean(selectByQuery.beanName());
        }

        throw new NoSuchBeanDefinitionException("not bean");
    }

    private static void add(String key, Object value, Map<String, Method> methods, Object
            map, SelectById.SelectByIdName selectByIdName) throws IOException {
        add(key, value, methods, map, selectByIdName.name(), selectByIdName.fieldName(), selectByIdName.defaultValue());
    }

    private static void add(String key, Object value, Map<String, Method> methods, Object map, SelectById
            selectById) throws IOException {
        add(key, value, methods, map, selectById.name(), selectById.fieldName(), selectById.defaultValue());
    }

    private static void add(String key, Object value, Map<String, Method> methods, Object map, String
            name, String fieldName, String defaultValue) throws IOException {
        if (key.lastIndexOf("Id") > -1) {
            key = key.substring(0, key.lastIndexOf("Id"));
        }

        key = StringUtils.isNotBlank(name) ? name : key + firstCharacterUppercase(fieldName);

        defaultValue = StringUtils.isNotBlank(defaultValue) ? defaultValue : null;

        try {

            String getMethod = GET_METHOD_START + key;
            String setMethod = SET_METHOD_START + key;
            String setStringMethod = "setString" + key;
            if (map != null && !methods.containsKey(getMethod)) {
                Method get = getClass(map.getClass(), fieldName);
                methods.put(getMethod, get);
                if (get != null) {
                    get.setAccessible(true);

                    if (!methods.containsKey(setMethod)) {
                        Method set = getClass(value.getClass(), key, get.getReturnType());
                        methods.put(setMethod, set);
                        if (set != null) {
                            set.setAccessible(true);
                        }
                    }
                }
            }

            if (value != null && !methods.containsKey(setStringMethod)) {
                Method set = getClass(value.getClass(), key, String.class);
                methods.put(setStringMethod, set);
                if (set != null) {
                    set.setAccessible(true);
                }
            }

            if (methods.get(setMethod) == null || methods.get(getMethod) == null) {
                if (defaultValue != null && methods.get(setStringMethod) != null) {
                    methods.get(setStringMethod).invoke(value, defaultValue);
                }
                return;
            }

            if (map == null) {
                if (methods.get(setStringMethod) != null) {
                    methods.get(setStringMethod).invoke(value, defaultValue);
                }
            } else {
                Object get = methods.get(getMethod).invoke(map);

                if (get == null && methods.get(setStringMethod) != null) {
                    methods.get(setStringMethod).invoke(value, defaultValue);
                } else if (get != null) {
                    methods.get(setMethod).invoke(value, get);
                }
            }
        } catch (Exception ignored) {
        }
    }

    private static String firstCharacterUppercase(String str) {
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }

    private static Method getClass(Class classs, String key) {
        StringBuffer sb = new StringBuffer();
        sb.append("get");
        sb.append(key.substring(0, 1).toUpperCase());
        sb.append(key.substring(1));

        try {
            return classs.getDeclaredMethod(sb.toString());
        } catch (NoSuchMethodException e) {
            if (classs.getSuperclass() != null) {
                return getClass(classs.getSuperclass(), key);
            }
        }

        return null;
    }

    private static Method getClass(Class classs, String key, Class aClass) {
        StringBuffer sb = new StringBuffer();
        sb.append("set");
        sb.append(key.substring(0, 1).toUpperCase());
        sb.append(key.substring(1));

        try {
            return classs.getDeclaredMethod(sb.toString(), aClass);
        } catch (NoSuchMethodException e) {
            if (classs.getSuperclass() != null) {
                return getClass(classs.getSuperclass(), key, aClass);
            }
        }

        return null;
    }
}
